﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace OnlineScheduling.DomainModel
{
    class TotalRandomSolver: Solver
    {
        public TotalRandomSolver(Instance instance)
            : base(instance)
        { }

        private Random random=new Random((int)DateTime.Now.Ticks/1024);
        public override int Run()
        {
            int weightedSum = 0;
            int time = 0;

            //kopiec z kolejno odbieranymi zadaniami, sortowany wedlug priorytetu - tutaj według wartości waga/pozostały_czas
            receivedTasksByPrioritySorted = new Heap<Task>();


            currentyWorkingCPUs = new Heap<Processor>();


            //dopóki są nieodebrane zadania, lub zadania jeszcze nie szkończone
            //   O(N)
            while (!Instance.IsEmpty || currentyWorkingCPUs.Count > 0 ||
                   receivedTasksByPrioritySorted.Count > 0)
            {

                //pierwszy krok: odpowiedź na pytanie czy wcześniej jakieś zadanie się zakończy
                //czy wcześniej przybędzie nowe zadanie do kolejki - ustalenie które zdarzenie 
                //zajdzie jako pierwsze

                //Wydarzenie: Zakończenie się aktualnie wykonywanego zadania

                int timeOfFirstFinishing = -1;
                if (currentyWorkingCPUs.Count > 0)
                    timeOfFirstFinishing = currentyWorkingCPUs.Root.CurrentTask.ExpectedTimeToBeDone;

                //Wydarzenie: Przyszło nowe zadanie
                int firstNewComeIn = Instance.GetTimeOfNextTask();


                int eventId = -1;
                int minimum = -1;

                if (timeOfFirstFinishing != -1)
                {
                    minimum = timeOfFirstFinishing;
                    eventId = 1;
                }
                if (firstNewComeIn != -1 && (firstNewComeIn < minimum || minimum == -1))
                {
                    minimum = firstNewComeIn;
                    eventId = 2;
                }


                switch (eventId)
                {
                    // Jakieś zadanie się zakończyło
                    case 1:
                        time = timeOfFirstFinishing;
                        while (currentyWorkingCPUs.Count > 0 && currentyWorkingCPUs.Root.CurrentTask.ExpectedTimeToBeDone == time)
                        {
                            Processor processorThatFinished = currentyWorkingCPUs.Pop();

                            Task finished = processorThatFinished.StopProcessingTask(time);
                            //uaktualnienie sumy ważonej
                            weightedSum += finished.Weight*finished.TimeFinished;
                            //ten procesor jest już wolny - zwróć jego id do puli wolnych IDików
                            idListOfFreeCPUs.Add(processorThatFinished.Id);
                        }
                        break;

                    // Przybyło nowe zadanie - przyjmij je do kolejki - kopca
                    case 2:
                        time = firstNewComeIn;
                        //odbierz wszystkie zadanie przybyłe w tej chwili czasu
                        while (Instance.GetTimeOfNextTask() == time)
                        {
                            Task toPut = Instance.GetNextTask();
                            toPut.PriorityType = TasksPriorityType.ByWeightSRTDivision;  //ustal priorytet - tutaj Waga/pozostały_czas
                            //wstaw na kopiec
                            receivedTasksByPrioritySorted.Push(toPut);
                        }
                        break;
                }

                //Przydzielanie zadań na wolne procesory - jeśli to możliwe
                // O(m)
                while (receivedTasksByPrioritySorted.Count > 0 && idListOfFreeCPUs.Count > 0)
                {
                    int idOfCPU = idListOfFreeCPUs.First();
                    idListOfFreeCPUs.Remove(idListOfFreeCPUs.First());  // ten id już jest zajęty

                    Task toAssign = receivedTasksByPrioritySorted.Pop();  // pobierz zadanie o największym priorytecie
                    processors[idOfCPU].AssignTask(toAssign, time);
                    processors[idOfCPU].ProcessorsPriorityType = ProcessorsPriorityType.ByEarliestFinish;
                    currentyWorkingCPUs.Push(processors[idOfCPU]);  // procesor z tym zadaniem na kopiec - wdług czasu zakończenia
                }


                if (receivedTasksByPrioritySorted.Count > 0)
                {
                    double minimumValue = -1;
                    int index = -1;
                    Processor candidateToPreempt = null;

                    //wylosowanie trzech procesorow - jesli na ktoryms z nich jest mniejsza waga to usuniecie
                    for (int i = 0; i < 3; i++)
                    {
                        int id = random.Next(0, currentyWorkingCPUs.Count);
                        Processor candidate = currentyWorkingCPUs[id];

                        if (candidate.CurrentTask.PriorityValue < minimumValue || minimumValue == -1)
                        {
                            index = id; 
                            candidateToPreempt = candidate;
                            minimumValue = candidate.CurrentTask.PriorityValue;
                        }

                    }

                    if (candidateToPreempt.CurrentTask.CompareTo(receivedTasksByPrioritySorted.Root) == -1)
                    {
                        Processor thatWillPreemptCurrentTask = currentyWorkingCPUs.PopAtSpecificPont(index);
                        Task preemted = thatWillPreemptCurrentTask.StopProcessingTask(time);


                        preemted.PriorityType = TasksPriorityType.ByWeightSRTDivision;
                        receivedTasksByPrioritySorted.Push(preemted);

                        thatWillPreemptCurrentTask.AssignTask(receivedTasksByPrioritySorted.Pop(), time);
                        thatWillPreemptCurrentTask.ProcessorsPriorityType = ProcessorsPriorityType.ByEarliestFinish;
                        currentyWorkingCPUs.Push(thatWillPreemptCurrentTask);
                    }

                }

            }

            WeightedSum = weightedSum;
            return WeightedSum;
        }

    }
}
